#region Copyright Vanderlande Industries B.V. 2008-2011
//
// Copyright (c) 2008-2011 Vanderlande Industries.
// All rights reserved.
//
// @file:     ConnectionService.cs
// @version:  %version: 39 %
// %date_created: donderdag 21 juni 2012 8:41:30 %
//
// The copyright to the computer program(s) herein is the property of
// Vanderlande Industries. The program(s) may be used and/or copied
// only with the written permission of the owner or in accordance with
// the terms and conditions stipulated in the contract under which the
// program(s) have been supplied.
#endregion

using System;
using System.Reflection;
using System.Threading;
using System.Windows.Forms;
//using VI.Fsc.OperatorGui.WebReferences.Opc;
//using VI.Fsc.OperatorGui.WebReferences.UserEvents;
using VI.Fsc.OperatorGui.Models.Fsc.Configuration;
using VI.Fsc.OperatorGui.Models.Application.Util;
using VI.Fsc.OperatorGui.WebReferences.Opc;
using VI.Fsc.OperatorGui.WebReferences.UserEvents;

namespace VI.Fsc.OperatorGui.Models.Application
{
    // Creates some delegate objects.
    public delegate void ConnectionStatusChangedHandler(bool isOnline, bool notifyUser);

    /// <summary>
    /// This abstract class encapsulates the basic functionality of a connection service.
    /// It handles the online status of the connection, the start and stop of the service as well.
    /// It is abstract since each connection needs to provide a way to reconnect, and a way to
    /// determine if the server is running, plus any other details related to the specific
    /// server to which the connection is made to.
    /// </summary>
    public abstract class CConnectionService
    {
        volatile protected bool isOnline = false;
        volatile protected bool isOnlineStatusSet = false;
        volatile protected bool isReconnecting = false;

        protected object _isOnlineLockObject = new object();

        private Thread reconnectionThread;

        public event ConnectionStatusChangedHandler ConnectionStatusChanged;

        /// <summary>
        /// Initializes the service.
        /// </summary>
        protected CConnectionService()
        {
            this.isOnlineStatusSet = false;
        }

        public void Init()
        {
            // Initializes the service only if not started before or simultaneously, to
            // prevent having 2 threads reconnecting at the same time.
            // deor, 01.09.2011: It is not clear what is intended with the use of the following control variable
            // and whether it is indeed achieved.
            if (!isReconnecting)
            {
                IsOnline = false;

                // Start connecting to the server, in a new thread to prevent blocking the UI.
                reconnectionThread = new Thread(TryToReconnect);
                reconnectionThread.IsBackground = true;
                reconnectionThread.Start();
            }
        }

        public void StopConnection()
        {
            // Sets the internal flag, without notifying.
            lock (_isOnlineLockObject)
            {
                isOnline = false;
            }

            if ((null != reconnectionThread) && reconnectionThread.IsAlive)
            {
                int waitMilliseconds = 200;
                //Logger.Debug(MethodBase.GetCurrentMethod(), "Trying to stop thread " +
               //     this.reconnectionThread + " gracefully.");
                bool stopped = this.reconnectionThread.Join(waitMilliseconds);
                if (!stopped)
                {
                    //Logger.Debug(MethodBase.GetCurrentMethod(), "Thread was not stopped - abort.");
                    try
                    {
                        this.reconnectionThread.Abort();
                    }
                    catch (ThreadAbortException ex)
                    {
                        //Logger.Info(MethodBase.GetCurrentMethod(), "Needed to abort thread: " + ex.Message);
                    }
                }
            }

            //Logger.Exiting(MethodBase.GetCurrentMethod());
        }

        public bool IsOnline
        {
            get
            {
                return this.isOnline;
            }
            set
            {
                lock (_isOnlineLockObject)
                {
                    //Logger.InfoFormat(
                    //    MethodBase.GetCurrentMethod(), 
                    //    "ConnectionService type = {0} | (old)IsOnline = {1} | (new)IsOnline = {2}", 
                    //    this.GetType(), isOnline, value);

                    bool valueChanged = false;

                    // If the value has not being set yet for the first time:
                    if (!isOnlineStatusSet)
                    {
                        // Set the "value already set" flag to true.
                        this.isOnlineStatusSet = true;
                        // 1Indicate that the value of the connection status changed.
                        valueChanged = true;
                    }
                    else if (isOnline != value)
                    {
                        // If the value was already set, but it now changed:
                        // Indicate that the value of the connection status changed.
                        valueChanged = true;
                    }

                    // If the value changed:
                    if (valueChanged)
                    {
                        // Set the new value.
                        isOnline = value;
                        // Notify about the change in the connection status.
                        if (ConnectionStatusChanged != null)
                        {
                            ConnectionStatusChanged(IsOnline, true);
                        }

                        // If we are now offline, start the reconnection try out.
                        if (!isOnline)
                        {
                            TryToReconnect();
                        }
                    }
                }
            }
        }

        virtual public void Restart()
        {
            isOnline = false;
            isOnlineStatusSet = true;
            isReconnecting = false;
        }

        abstract public bool IsServerRunning();

        abstract protected void TryToReconnect();
    }

    /// <summary>
    /// This service is in charge of tracking/handlling the connection of the
    /// application to the OPC server.
    /// This service is a singleton.
    /// </summary>
    public class COPCServerConnectionService : CConnectionService
    {
        private static COPCServerConnectionService instance;

        private readonly object _opcServerLock = new object();

        private OpcXmlDaWebService opcServer;

        public OpcXmlDaWebService OPCServer
        {
            get
            {
                lock (_opcServerLock)
                {
                    if (null == opcServer)
                    {
                        string url = CFscConfiguration.Instance.GetOpcWebServiceSettings().IpAddress;
                        opcServer = new OpcXmlDaWebService(url);
                    }

                    return opcServer;
                }
            }
            private set
            {
                lock (_opcServerLock)
                {
                    opcServer = value;
                }
            }
        }

        /// <summary>
        /// Initializes the service.
        /// </summary>
        private COPCServerConnectionService()
        {
        }

        /// <summary>
        /// Getter for the singleton instance of the service.
        /// </summary>
        public static COPCServerConnectionService Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = new COPCServerConnectionService();
                }

                return instance;
            }
        }

        override public void Restart()
        {
            base.Restart();

            OPCServer = null;
            string url = CFscConfiguration.Instance.GetOpcWebServiceSettings().IpAddress;
            OPCServer = new OpcXmlDaWebService(url);
            
            Init();
        }

        override public bool IsServerRunning()
        {
            bool isRunning = false;

            try
            {
                WebReferences.Opc.ItemValue isReady = CFscUtility.ReadItem("Connections/OPC/IsReady");

                if (isReady != null)
                {
                    isRunning = Convert.ToBoolean(isReady.Value.ToString());
                }
                else
                {
                    //Logger.Error(MethodBase.GetCurrentMethod(), "Item is null - No connection yet");
                }
            }
            catch (CFscException fscEx)
            {
                //Logger.Error(MethodBase.GetCurrentMethod(), fscEx.Message);
            }
            catch (Exception e)
            {
               // Logger.Error(MethodBase.GetCurrentMethod(), e.Message);
            }

            return isRunning;
        }

        /// <summary>
        /// Get the date and time of the server, synchronously.
        /// </summary>
        /// <returns>The date/time of the server.</returns>
        public virtual DateTime GetServerDateTime()
        {
            //Logger.Entering(MethodBase.GetCurrentMethod());

            DateTime serverDateTime = DateTime.MinValue;
            WebReferences.Opc.ServerStatus serverStatus = new WebReferences.Opc.ServerStatus();

            try
            {
                // Get the status from the sever.
                WebReferences.Opc.ReplyBase replyInfo = this.OPCServer.GetStatus("en-US", "reconnection", out serverStatus);

                // If the server is running.
                if (serverStatus.State == WebReferences.Opc.serverState.running)
                {
                    // Calculate how many seconds ago did the server start running.
                    serverDateTime = replyInfo.FscDateTime;
                }
            }
            catch (System.Net.WebException webException)
            {
                //Logger.Error(MethodBase.GetCurrentMethod(), String.Format("[GetServerDateTime] {0}", webException));

                // If there was a problem asking the status to the server, retry.
                IsOnline = false;
            }

            //Logger.Exiting(MethodBase.GetCurrentMethod());
            return serverDateTime;
        }

        override protected void TryToReconnect()
        {
            try
            {
                // This non-terminating loop is intentional. The loop will be aborted from the outside.
                while (!this.isReconnecting)
                {
                    // TODO: This is not thread save use lock
                    this.isReconnecting = true;

                    try
                    {
                        if (IsServerRunning())
                        {
                            // If the server is running, then set the status as online.
                            IsOnline = true;
                            this.isReconnecting = false;
                            //Logger.Info(MethodBase.GetCurrentMethod(), "Connected to the OPC server.");
                        }
                        else
                        {
                            //Logger.Info(MethodBase.GetCurrentMethod(),
                            //    "Not connected to the OPC server, will retry to connect.");
                        }
                    }
                    catch (System.Net.WebException webException)
                    {
                        // Add a log entry.
                        //Logger.Debug(MethodBase.GetCurrentMethod(), webException.Message);

                        // If there was a problem asking the status to the server, retry.
                        IsOnline = false;
                    }

                    // Try again in one second
					// When the OPC server is online we don't have to check every second
                    if (!IsOnline)
                    {
                        System.Threading.Thread.Sleep(1000);
                    }
                    else
                    {
                        System.Threading.Thread.Sleep(15000);
                    }
                    // Clean the reconnection flag.
                    this.isReconnecting = false;
                }
            }
            catch (ThreadAbortException e)
            {
                //Logger.Info(MethodBase.GetCurrentMethod(), "Trying to abort thread. " + e);
            }
            catch (Exception ex) // As in the RCF-Main program we exit if an exception arrives unhandled here.
            {
                //Logger.Fatal(MethodBase.GetCurrentMethod(), "Uncaught exception occurred.", ex);
                MessageBox.Show("Uncaught exception occurred: " + ex.Message + "\nSee log for details.", "Error",
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
                Environment.Exit(1);
            }
        }
    }

    /// <summary>
    /// This service is in charge of the connection of the application to the soap server.
    /// This service is a singleton.
    /// </summary>
    public class CSoapServerConnectionService : CConnectionService
    {
        private static CSoapServerConnectionService instance;

        private CUserEventsService userEventsService;

        public CUserEventsService UserEventsService
        {
            get
            {
                if (null == userEventsService)
                {
                    userEventsService = new CUserEventsService();
                    userEventsService.Url = CFscConfiguration.Instance.GetUserEventsWebServiceSettings().IpAddress;
                }

                return userEventsService;
            }
        }

        /// <summary>
        /// Initializes the service.
        /// </summary>
        private CSoapServerConnectionService()
        {
        }

        /// <summary>
        /// Getter for the singleton instance of the service.
        /// </summary>
        public static CSoapServerConnectionService Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = new CSoapServerConnectionService();
                }

                return instance;
            }
        }

        override public void Restart()
        {
            base.Restart();

            userEventsService = null;
            userEventsService = new CUserEventsService();
            userEventsService.Url = CFscConfiguration.Instance.GetUserEventsWebServiceSettings().IpAddress;
            Init();
        }

        override public bool IsServerRunning()
        {
            bool isRunning = true;

            try
            {
                this.UserEventsService.checkConnection();
            }
            catch (Exception ex)
            {
                isRunning = false;

                //Logger.Info(MethodBase.GetCurrentMethod(), ex.Message);
            }

            return isRunning;
        }

        override protected void TryToReconnect()
        {
            //Logger.Exiting(MethodBase.GetCurrentMethod());

            try
            {
                // This non-terminating loop is intentional. The loop will be aborted from the outside.
                // isReconnecting is only used to prevent having 2 threads reconnecting at the same time.
                while (!this.isReconnecting)
                {
                    this.isReconnecting = true;

                    if (IsServerRunning())
                    {
                        IsOnline = true;
                        //Logger.Info(MethodBase.GetCurrentMethod(), "Connected to the user events server.");
                    }
                    else
                    {
                        IsOnline = false;
                        //Logger.Info(MethodBase.GetCurrentMethod(),
                        //    "Not connected to the user events server, will retry to connect.");
                    }
                    // Waits 6 seconds before next try.
					// When the SOAP server is online we don't have to check that much
                    if (!IsOnline)
                    {
                        System.Threading.Thread.Sleep(6000);
                    }
                    else
                    {
                        System.Threading.Thread.Sleep(15000);
                    }
                    this.isReconnecting = false;
                }
            }
            catch (ThreadAbortException e)
            {
                //Logger.Info(MethodBase.GetCurrentMethod(), "Trying to abort thread. " + e);
            }
            catch (Exception ex) // As in the RCF-Main program we exit if an exception arrives unhandled here.
            {
                //Logger.Fatal(MethodBase.GetCurrentMethod(), "Uncaught exception occurred.", ex);
                MessageBox.Show("Uncaught exception occurred: " + ex.Message + "\nSee log for details.", "Error",
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
                Environment.Exit(1);
            }

            //Logger.Exiting(MethodBase.GetCurrentMethod());
        }
    }
}
